一个好的HTTP缓存策略可以显著的提高web应用的表现和客户的体验。Cache-ControlHTTP相应头主要就是负责这个,以及Last-ModifiedETag等条件头。
Cache-ControlHTTP响应头建议私有缓存(比如,浏览器)和公有缓存(比如,代理)如何为了重用而缓存HTTP响应。
ETag(entity tag)是一个HTTP响应头,由兼容HTTP/1.1的web服务器返回,用来检测给定的URL是否发生改变。它可以被看作时更加复杂的Last-Modified头文件。当一个服务器返回了含有ETag头的响应,客户端会在之后的GETs请求中,在If-None-Match使用这个头。如果这个内容没有被改变,服务器会返回304: Not Modified
这节描述了在Spring Web MVC应用中配置HTTP缓存的不同方式。

22.14.1 Cache-Control HTTP header

Spring Web MVC应用支持多种案例和多种方法配置"Cache-Control"头。虽然在RFC 7234的5.2.2节中已经完整的描述了头和它可能的指定,这里还是列出一些符合常用情况的方法。
Spring Web WVC可以通过它的API来方便的配置:setCachePeriod(int seconds): -1值不会生成Cache-Control响应头。
0值将会使用Cache-Control:on-store指定阻止缓存。
* n > 0值会使用Cache-Control:max-age=n将给定的响应缓存n秒。

CacheControl构建器类简单的描述了可用的"Cache-Control"指令并让你更容易的构建自己的缓存策略。一旦创建完成,CacheControl实例可以被多个Spring Web MVC API接受作为参数。

// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS)
                                    .noTransform().cachePublic();

22.14.2 HTTP caching support for static resources

应该要为静态资源选择合适的Cache-Control和条件头来获得最佳的性能。如果正确配置ResourceHttpRequestHandler提供静态资源,不仅可以通过读文件元数据中的Last-Modified头,还可以使用Cache-Control头。
你可以设置ResourceHttpRequestHandlercachePeriod属性或是使用CacheControl实例,它们都支持更具体的指令:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public-resources/")
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic());
    }

}

使用XML配置时:

<mvc:resources mapping="/resources/**" location="/public-resources/">
    <mvc:cache-control max-age="3600" cache-public="true"/>
</mvc:resources>

22.14.3 Support for the Cache-Control, ETag and Last-Modified response headers in Controllers

控制器可以支持Cache-ControlETag,和If-Modified-Since的HTTP请求;事实上如果想在响应中设置Cache-Control头的话,也建议这么做。它会计算给定请求的lastModified的long值或是ETag值,将它和请求的If-Modified-Since头的值比较,并可能返回一个状态码为304(Not Modified)的响应。
正如在"Using HttpEntity"这节中描述的,控制器可以通过HttpEntity类型跟请求/响应交互。控制器可以像这样返回的ResponseEntity包含响应的HTTP缓存信息:

@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
                .ok()
                .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
                .eTag(version) // lastModified is also available
                .body(book);
}

这样做不仅会让响应包含'ETag'Cache-Control头,当客户端发送的条件头符合控制器设置的缓存信息,它也会将响应的转变成HTTP 304 Not Modified的响应且响应体为空。
@RequestMapping方法可能也希望有相同的行为。看下面如何实现:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {

    long lastModified = // 1. application-specific calculation

    if (request.checkNotModified(lastModified)) {
        // 2. shortcut exit - no further processing necessary
        return null;
    }

    // 3. or otherwise further request processing, actually preparing content
    model.addAttribute(...);
    return "myViewName";
}

这里有两个关键的元素:调用request.checkNotModified(lastModified)和返回null。前者会在它返回true时设置响应的装填和头。后者,结合了前者,会使得Spring MVC不会在继续处理请求。
它有三种变体: request.checkNotModified(lastModified)会将lastModified和请求头中的If-Modified-Since或是If-Unmodified-Since比较
request.checkNotModified(eTag)会将eTag和请求头中的If-None-Match比较 * request.checkNotModified(eTag, lastModified)会将两者都进行比较,必须两个条件都要符合

但收到带条件的GET/HEAD请求,checkNotModified会检查资源是否没被修改过,如果是,它会发出HTTP 304 Not Modified的响应。至于POST/PUT/DELETE请求,checkNotMidified会检查资源是否没被修改过,如果是,会生成HTTP 409 Precondition Failed响应,避免并发的修改。

22.14.4 Shallow ETag support

对ETags的支持是有Servlet过滤器的ShallowEtagHeaderFilter提供。它是一个普通的Servlet过滤器,因此可以和任何web狂气结合使用。ShallowEtagHeaderFilter过滤器创造了所谓的浅ETags(和深ETags相反,之后会介绍)。过滤器缓存了被渲染的JSP(或是其他内容),并在其上生成了MD5码,并将它作为响应的ETag头返回。当下次客户端请求相同的资源时,会用它的哈希作为If-None-Match值。过滤器会检测这点,再次渲染视图,并比较两个哈希值,如果它们相同,则会返回304
注意,这个策略只会节省贷款而不会节省CPU,因为响应都必须在每个请求计算完成后。另一种策略是控制器级别的(上面所讨论的),可以节省贷款,并避免计算。
这个过滤器有个writeWeakTag属性用来配置过滤器去写Weak ETags,像RFC 7323 2.3节中定义的那样:W/"02a2d595e6ed9a0b24f027f2b63b134d6"
你可以这样在web.xml中配置ShallowEtagHeaderFilter

<filter>
    <filter-name>etagFilter</filter-name>
    <filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
    <!-- Optional parameter that configures the filter to write weak ETags
    <init-param>
        <param-name>writeWeakETag</param-name>
        <param-value>true</param-value>
    </init-param>
    -->
</filter>

<filter-mapping>
    <filter-name>etagFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

或是在Servlet 3.0+的环境中:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new ShallowEtagHeaderFilter() };
    }

}

22.15,"Code-based Servlet container initialzation"获取更多信息。